/***************************************************************************
 * This program is free software; you can redistribute it and/or           *
 * modify it under the terms of the GNU General Public License             *
 * as published by the Free Software Foundation; either version 2          *
 * of the License, or (at your option) any later version.                  *
 *                                                                         *
 * This program is distributed in the hope that it will be useful,         *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of          *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the           *
 * GNU General Public License for more details.                            *
 *                                                                         *
 * You should have received a copy of the GNU General Public License       *
 * along with this program; if not, write to the Free Software Foundation, *
 * Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.         *
 *                                                                         *
 *   Copyright (C) 2007,2008 by Ivan Vucica                                *
 *   ivucica@gmail.com                                                     *
 ***************************************************************************/

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <map>
#include "procedure.h"
#include "token.h"
#include "logocontext.h"
#include "logointerpreter.h"
#include "logolist.h"
#include "turtle.h"
#include "utils.h"

void onlogo_printf (const char* txt, ...) ;

Color onlogo_screen [640*480]={{1,1,1,1}};
Turtle turtle;



Token prm_print (PROCPARAMS) {
	// argument count: 1
	// supports arbitrary arguments: yes
	static std::string s;
	for (std::vector<Token>::const_iterator it = t.begin(); it != t.end(); it++) {
		switch (it->Evaluate(global, local).type) {
			case Token::TT_WORD:
				onlogo_printf ("%s ", it->Evaluate(global, local).text.c_str());
				break;
			case Token::TT_NUMBER:
				onlogo_printf ("%g ", it->Evaluate(global, local).number);
				break;
			case Token::TT_LIST: // remove [ and ]
				s = it->Evaluate(global, local).text;
				s = s.substr(1, s.size()-2);

				onlogo_printf ("%s ", s.c_str());
				break;
			case Token::TT_UNDEFINED:
				throw std::string("PRINT's ") + orderednumber(it - t.begin() + 1) + " argument is an undefined " + it->Describe();
				break;
			default:
				throw std::string("PRINT does not like ") + it->Describe() + " (" + it->Evaluate(global, local).Describe() + ") as its " + orderednumber(it - t.begin() + 1) + " input";
		}



	}
	onlogo_printf("\n");
	return Token(Token::TT_UNDEFINED);
}

Token prm_pots (PROCPARAMS) {
	// argument count: 0
	// supports arbitrary arguments: yes (but ignores them)
	onlogo_printf ("*** Procedures: \n");
	for (std::map<std::string, procedure_t>::iterator it = global.procedures.begin(); it!=global.procedures.end();it++) {
	if (!(it->second.primitive))
			onlogo_printf ("> %s\n", it->first.c_str());
	}
	onlogo_printf ("*** End of procedure list\n");
	return Token(Token::TT_UNDEFINED);
}

Token prm___variables (PROCPARAMS) {
	// argument count: 0
	// supports arbitrary arguments: yes (but ignores them)
	onlogo_printf ("*** Variables: \n");
	for (std::map<std::string, Token>::iterator it = global.variables.begin(); it!=global.variables.end();it++) {
			onlogo_printf ("%s = ", it->first.c_str());
			onlogo_printf ("%s [GLOBAL]\n", it->second.Evaluate(global, local).text.c_str());
			//onlogo_printf ("%s = %s\n", it->first.c_str(), it->second.Evaluate(global, local).text.c_str());
	}

	for (std::map<std::string, Token>::iterator it = local.variables.begin(); it!=local.variables.end();it++) {
			onlogo_printf ("%s = %s [LOCAL]\n", it->first.c_str(), it->second.Evaluate(global, local).text.c_str());
	}
	onlogo_printf ("*** End of variable list\n");
	return Token(Token::TT_UNDEFINED);
}

#include <iostream>
Token prm_make (PROCPARAMS) {
	if (t.size() != 2) {
		throw(std::string("MAKE takes two input."));
	}
	std::vector<Token>::const_iterator x = t.begin();
	std::vector<Token>::const_iterator y = x+1;
	Token res = y->Evaluate(global, local);


	if (local.variables.find(x->Evaluate(global, local).text) != local.variables.end()) {
		local.variables[x->Evaluate(global, local).text] = Token(std::string(res.type == Token::TT_WORD  ? "\"" : "") + res.text);
	} else {
		global.variables[x->Evaluate(global, local).text] = Token(std::string(res.type == Token::TT_WORD  ? "\"" : "") + res.text);
	}
	return Token(Token::TT_UNDEFINED);
}

Token prm_forward (PROCPARAMS) {
	if (t.size() != 1) {
		throw(std::string("FORWARD takes one input."));
	}
	if (t[0].Evaluate(global, local).type != Token::TT_NUMBER) {
		throw std::string("FORWARD does not like ") + t[0].Describe() + " (" + t[0].Evaluate(global, local).Describe() + ") as its first input";
	}
	int firstx = turtle.GetX(); int firsty = turtle.GetY();

	turtle.Move(t[0].Evaluate(global, local).number);

	if (turtle.IsPenDown())
		for (double pcnt = 0; pcnt < 1; pcnt += 0.001) {
			int cx = (int)turtle.GetX() * pcnt + firstx * (1.-pcnt);
			int cy = (int)turtle.GetY() * pcnt + firsty * (1.-pcnt);
			if (cx > -320 && cx < 320 && cy > -240 && cx < 240)
				onlogo_screen[(int)((cx+320.) + 640.*(cy+240.))] = turtle.GetColor();
		}
	return Token(Token::TT_UNDEFINED);

}

Token prm_back (PROCPARAMS) {
	if (t.size() != 1) {
		throw(std::string("BACK takes one input."));
	}
	if (t[0].Evaluate(global, local).type != Token::TT_NUMBER) {
		throw std::string("BACK does not like ") + t[0].Describe() + " (" + t[0].Evaluate(global, local).Describe() + ") as its first input";
	}
	int firstx = turtle.GetX(); int firsty = turtle.GetY();
	turtle.Move(-t[0].Evaluate(global, local).number);


	if (turtle.IsPenDown())
		for (double pcnt = 0; pcnt < 1; pcnt += 0.001) {
			int cx = (int)turtle.GetX() * pcnt + firstx * (1.-pcnt);
			int cy = (int)turtle.GetY() * pcnt + firsty * (1.-pcnt);
			if (cx > -320 && cx < 320 && cy > -240 && cx < 240)
				onlogo_screen[(int)((cx+320.) + 640.*(cy+240.))] = turtle.GetColor();
		}
	return Token(Token::TT_UNDEFINED);
}

Token prm_right (PROCPARAMS) {
	if (t.size() != 1) {
		throw(std::string("RIGHT takes one input."));
	}
	if (t[0].Evaluate(global, local).type != Token::TT_NUMBER) {
		throw std::string("RIGHT does not like ") + t[0].Describe() + " (" + t[0].Evaluate(global, local).Describe() + ") as its first input";
	}
	turtle.Rotate(t[0].Evaluate(global, local).number);
	return Token(Token::TT_UNDEFINED);
}

Token prm_left (PROCPARAMS) {
	if (t.size() != 1) {
		throw(std::string("LEFT takes one input."));
	}
	if (t[0].Evaluate(global, local).type != Token::TT_NUMBER) {
		throw std::string("LEFT does not like ") + t[0].Describe() + " (" + t[0].Evaluate(global, local).Describe() + ") as its first input";
	}
	turtle.Rotate(-t[0].Evaluate(global, local).number);
	return Token(Token::TT_UNDEFINED);
}

Token prm_showturtle (PROCPARAMS) {
	turtle.Show();
	return Token(Token::TT_UNDEFINED);
}
Token prm_hideturtle (PROCPARAMS) {
	turtle.Hide();
	return Token(Token::TT_UNDEFINED);
}
Token prm_setpc (PROCPARAMS) {
	if (t.size() != 1) {
		throw(std::string("SETPC takes one input."));
	}
	if (t[0].Evaluate(global, local).type != Token::TT_NUMBER) {
		throw std::string("SETPC does not like ") + t[0].Describe() + " (" + t[0].Evaluate(global, local).Describe() + ") as its first input";
	}
	if (t[0].Evaluate(global, local).number > 15 || t[0].Evaluate(global, local).number < 0) {
		throw std::string("SETPC does not like ") + t[0].Describe() + " (" + t[0].Evaluate(global, local).Describe() + ") as its first input";
	}
	Color c;
	c = (char)t[0].Evaluate(global, local).number;
	turtle.SetColor(c);
	return Token(Token::TT_UNDEFINED);
}
Token prm_pendown (PROCPARAMS) {
	turtle.PenDown();
	return Token(Token::TT_UNDEFINED);
}
Token prm_penup(PROCPARAMS) {
	turtle.PenUp();
	return Token(Token::TT_UNDEFINED);
}

Token prm_clearscreen(PROCPARAMS) {
	for (int i = 0; i < 640*480; i++) {
		onlogo_screen[i] = 0;
	}
	return Token(Token::TT_UNDEFINED);
}

Token prm_repeat(PROCPARAMS) {
	if (t.size() != 2) {
		throw(std::string("REPEAT takes two inputs."));
	}
	if (t[0].Evaluate(global, local).type != Token::TT_NUMBER) {
		throw std::string("REPEAT does not like ") + t[0].Describe() + " (" + t[0].Evaluate(global, local).Describe() + ") as its first input";
	}
	if (t[0].Evaluate(global, local).number < 1) {
		throw std::string("REPEAT does not like ") + t[0].Describe() + " (" + t[0].Evaluate(global, local).Describe() + ") as its first input";
	}
	if (t[1].Evaluate(global, local).type != Token::TT_LIST) {
		throw std::string("REPEAT does not like ") + t[1].Describe() + " (" + t[1].Evaluate(global, local).Describe() + ") as its second input";
	}
	LogoInterpreter li(t[1].Evaluate(global, local).text.substr(1, t[1].Evaluate(global, local).text.size()-2));

	for (int i=0;i<t[0].Evaluate(global, local).number; i++) {
		li.Execute(global, local);
	}
	return Token(Token::TT_UNDEFINED);
}
Token prm_for(PROCPARAMS) {
	if (t.size() != 4 && t.size() != 5 )  {
		throw(std::string("FOR takes four to five inputs."));
	}
	if (t[0].Evaluate(global, local).type != Token::TT_WORD) {
		throw std::string("FOR does not like ") + t[0].Describe() + " (" + t[0].Evaluate(global, local).Describe() + ") as its first input";
	}
	if (t[1].Evaluate(global, local).type != Token::TT_NUMBER) {
		throw std::string("FOR does not like ") + t[0].Describe() + " (" + t[0].Evaluate(global, local).Describe() + ") as its second input";
	}
	if (t[2].Evaluate(global, local).type != Token::TT_NUMBER) {
		throw std::string("FOR does not like ") + t[0].Describe() + " (" + t[0].Evaluate(global, local).Describe() + ") as its third input";
	}

	if (t[3].Evaluate(global, local).type != Token::TT_LIST) {
		throw std::string("FOR does not like ") + t[3].Describe() + " (" + t[3].Evaluate(global, local).Describe() + ") as its fourth input";
	}
	double step=1;
	if (t.size() == 5) {
		if (t[4].Evaluate(global, local).type != Token::TT_NUMBER) {
			throw std::string("FOR does not like ") + t[4].Describe() + " (" + t[4].Evaluate(global, local).Describe() + ") as its fifth input";
		}
		if (t[4].Evaluate(global, local).number == 0) {
			throw std::string("FOR does not like ") + t[4].Describe() + " (" + t[4].Evaluate(global, local).Describe() + ") as its fifth input";
		}
		step = t[4].number;
	}


	LogoInterpreter li(t[3].Evaluate(global, local).text.substr(1, t[3].Evaluate(global, local).text.size()-2));
	std::string arg0 = t[0].Evaluate(global, local).text;
	double arg1 = t[1].Evaluate(global, local).number;
	double arg2 = t[2].Evaluate(global, local).number;
	std::stringstream arg_s;

	if (arg1 > arg2 && step > 0 || arg1 < arg2 && step < 0)
		return Token(Token::TT_UNDEFINED);

	for (double i=arg1;step > 0 ? i<=arg2 : i >= arg2; i+=step) {
		// FIXME (Khaos#1#) it'd be way faster to directly call prm_make instead of calling it via a logointerpreter
		arg_s.str("");
		arg_s << i;
		LogoInterpreter li2(std::string("MAKE \"") + arg0 + " " + arg_s.str());
		li2.Execute(global, local);

		li.Execute(global, local);
	}

	return Token(Token::TT_UNDEFINED);
}

Token prm_if(PROCPARAMS) {

	if (t.size() != 2 && t.size() != 3)  {
		throw(std::string("IF takes two to three inputs."));
	}

	if (t[0].Evaluate(global, local).type != Token::TT_WORD) {
		throw std::string("IF does not like ") + t[0].Describe() + " (" + t[0].Evaluate(global, local).Describe() + ") as its first input";
	}
	if (t[0].Evaluate(global, local).text != "TRUE"  && t[0].Evaluate(global, local).text != "FALSE") {
		throw std::string("IF does not like ") + t[0].Describe() + " (" + t[0].Evaluate(global, local).Describe() + ") as its first input";
	}

	if (t[1].Evaluate(global, local).type != Token::TT_LIST) {
		throw std::string("IF does not like ") + t[1].Describe() + " (" + t[1].Evaluate(global, local).Describe() + ") as its second input";
	}
	if (t.size() == 3) {
		if (t[2].Evaluate(global, local).type != Token::TT_LIST) {
			throw std::string("IF does not like ") + t[2].Describe() + " (" + t[2].Evaluate(global, local).Describe() + ") as its third input";
		}
	}

	if (t[0].Evaluate(global, local).text == "TRUE") {
		LogoInterpreter li(t[1].Evaluate(global, local).text.substr(1, t[1].Evaluate(global, local).text.size()-2));
		li.Execute(global, local);
	} else if (t.size() == 3) {
		LogoInterpreter li(t[2].Evaluate(global, local).text.substr(1, t[2].Evaluate(global, local).text.size()-2));
		li.Execute(global, local);
	}

	return Token(Token::TT_UNDEFINED);
	//throw(std::string("IF[ELSE] is not finished."));
}

Token prm_and (PROCPARAMS) {
	if (t.size() < 2)  {
		throw(std::string("AND takes two or more inputs."));
	}


	for (int i = 0; i < t.size(); i++) {
		if (t[i].Evaluate(global, local).type != Token::TT_WORD) {
			throw std::string("AND does not like ") + t[i].Describe() + " (" + t[i].Evaluate(global, local).Describe() + ") as its " + orderednumber(i) + "input";
		}
		if (t[i].Evaluate(global, local).text != "TRUE"  && t[i].Evaluate(global, local).text != "FALSE") {
			throw std::string("AND does not like ") + t[i].Describe() + " (" + t[i].Evaluate(global, local).Describe() + ") as its " + orderednumber(i) + "input";
		}
		if (t[i].Evaluate(global, local).text == "FALSE") return Token("\"FALSE");
	}
	return Token("\"TRUE");

}

Token prm_first (PROCPARAMS) {
    if (t.size() != 1) {
        throw(std::string("FIRST takes one input."));
    }
    Token teval = t[0].Evaluate(global,local);
    Token::tokentype_t tt  = teval.type;
    if (tt != Token::TT_WORD && tt != Token::TT_LIST) {
        throw std::string("FIRST does not like ") + t[0].Describe() + " (" + teval.Describe() + ") as its first input";
    }

    if (tt == Token::TT_WORD)
        return Token(std::string("\"") + teval.text[0]);

    // otherwise it's a list
    LogoList l(teval.text.substr(1, teval.text.size()-2));
    return l.GetElement(0);

}

Token prm_bye (PROCPARAMS) {
	onlogo_printf ("Goodbye :)\n");
	exit(EXIT_SUCCESS);
}
